package communication;

import java.util.Hashtable;

import communication.definitions.DeviceType;
import communication.definitions.Message;
import communication.definitions.ReceivedFunction;
import communication.exceptions.InvalidArgumentsException;
import communication.exceptions.InvalidFunctionException;

import device.Device;

/**
 * The <code>Dispatcher</code> class is used to store all devices which have
 * been initialized on the client. The <code>Dispatcher</code> is also
 * responsible for receiving messages sent by the <code>Communicator</code> and
 * passing them along to the correct <code>Device</code>.
 * <p>
 * An instance of this object must be passed into the a new
 * <code>Communicator</code> in order for it to work. Even after the
 * <code>Dispatcher</code> is part of a <code>Communicator</code> more devices
 * can be added to it.
 * 
 * @author Peter Kalauskas
 */
public class Dispatcher {
	private volatile Hashtable<Identifiable, Device> devices;

	/**
	 * Constructs a new dispatcher object.
	 */
	public Dispatcher() {
		devices = new Hashtable<Identifiable, Device>();
	}

	/**
	 * Adds a new device to this dispatcher. This must be done in order for the
	 * device to receive messages. If the device shares the same type and ID as
	 * another device in this dispatcher then the previous one will be
	 * overwritten.
	 * 
	 * @param d
	 *            the device to be added
	 * @return the previous device associated with this devices type and ID or
	 *         null if no such device existed
	 */
	public Device addDevice(Device d) {
		return devices.put(new Identifiable(d), d);
	}

	/**
	 * Removes a device from the dispatcher; this device will no longer receive
	 * messages.
	 * 
	 * @param d
	 *            the device to be removed
	 * @return the device which was removed from the dispatcher or
	 *         <code>null</code> if the device was not part of the dispatcher
	 */
	public Device removeDevice(Device d) {
		return devices.remove(new Identifiable(d));
	}

	/**
	 * Passes along a message to the correct device.
	 * 
	 * @param m
	 *            the message to be passed to a device
	 * @throws InvalidArgumentsException
	 * @throws InvalidFunctionException
	 */
	public void dispatch(Message m) throws InvalidArgumentsException,
			InvalidFunctionException {
		Device d = devices.get(new Identifiable(m.getDevice(), m.getId()));
		d.receiveFunction((ReceivedFunction) m.getFunction(), m.getData());
	}

	/**
	 * The <code>Identifiable</code> class is used as a key for the devices
	 * added to the dispatcher.
	 * 
	 * @author Peter Kalauskas
	 */
	private class Identifiable {
		private DeviceType type;
		private byte id;

		/**
		 * Constructs a new identifiable with the type and id of the given
		 * device
		 * 
		 * @param d
		 *            the device to use for the type and id
		 */
		public Identifiable(Device d) {
			this.type = d.getType();
			this.id = d.getID();
		}

		/**
		 * Constructs a new identifiable with the given type and id.
		 * 
		 * @param type
		 * @param id
		 */
		public Identifiable(DeviceType type, byte id) {
			this.type = type;
			this.id = id;
		}

		/**
		 * Checks if this <code>Identifiable</code> is equal to another one.
		 */
		public boolean equals(Object o) {
			Identifiable d = (Identifiable) o;
			return this.type == d.type && this.id == d.id;
		}

		/**
		 * Generates a simple hash code for this identifiable.
		 */
		public int hashCode() {
			return this.type.byteValue() + this.id;
		}
	}
}
